Skip to content

feat(coroutine): 添加协程分组管理和优先级支持#82

Merged
GeWuYou merged 4 commits into
mainfrom
feat/coroutine-enhancements
Mar 6, 2026
Merged

feat(coroutine): 添加协程分组管理和优先级支持#82
GeWuYou merged 4 commits into
mainfrom
feat/coroutine-enhancements

Conversation

@GeWuYou

@GeWuYou GeWuYou commented Mar 6, 2026

Copy link
Copy Markdown
Owner
  • 实现协程分组功能,支持批量暂停、恢复和终止协程
  • 添加协程优先级系统,支持从最低到最高的5个优先级级别
  • 引入协程统计功能,跟踪启动、完成、失败数量及执行时间
  • 添加FakeTimeSource用于协程测试的时间控制
  • 实现按优先级排序的协程执行机制
  • 添加协程执行时间戳记录功能
  • 实现完整的协程统计报告生成功能

Summary by Sourcery

为调度器新增协程分组、优先级与统计能力,以及相应的抽象与测试。

New Features:

  • 引入协程优先级等级,并在调度器中按优先级顺序执行协程。
  • 添加协程分组支持,用于批量暂停、恢复、终止以及计数操作。
  • 提供协程统计接口及实现,用于跟踪数量、执行时间指标以及按优先级/标签划分的数据,并可在调度器上按需启用。
  • 暴露一个可控的 FakeTimeSource 实现,用于驱动可确定性的协程测试。

Enhancements:

  • 扩展协程元数据和槽位以存储优先级、分组信息以及开始时间戳,并相应更新调度器生命周期处理逻辑。

Tests:

  • 添加单元测试,覆盖协程统计行为与报告、分组管理操作以及基于优先级的执行顺序。
Original summary in English

Summary by Sourcery

Add coroutine grouping, prioritization, and statistics capabilities to the scheduler, along with supporting abstractions and tests.

New Features:

  • Introduce coroutine priority levels and execute coroutines in priority order within the scheduler.
  • Add coroutine grouping support for bulk pause, resume, kill, and counting operations.
  • Provide a coroutine statistics interface and implementation to track counts, execution time metrics, and per-priority/tag data, with optional enablement on the scheduler.
  • Expose a controllable FakeTimeSource implementation to drive deterministic coroutine tests.

Enhancements:

  • Extend coroutine metadata and slots to store priority, group, and start timestamps, and update scheduler lifecycle handling accordingly.

Tests:

  • Add unit tests covering coroutine statistics behavior and reporting, group management operations, and priority-based execution ordering.

- 实现协程分组功能,支持批量暂停、恢复和终止协程
- 添加协程优先级系统,支持从最低到最高的5个优先级级别
- 引入协程统计功能,跟踪启动、完成、失败数量及执行时间
- 添加FakeTimeSource用于协程测试的时间控制
- 实现按优先级排序的协程执行机制
- 添加协程执行时间戳记录功能
- 实现完整的协程统计报告生成功能
@deepsource-io

deepsource-io Bot commented Mar 6, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 16d8cad...e5bd972 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade  

Focus Area: Complexity
Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Mar 6, 2026 5:07a.m. Review ↗
Secrets Mar 6, 2026 5:07a.m. Review ↗

@sourcery-ai

sourcery-ai Bot commented Mar 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

为调度器新增协程分组和优先级支持,以及可选启用的统计子系统和配套的测试/工具(包括一个伪时间源)。

带统计信息的优先级协程运行与完成时序图

sequenceDiagram
    actor Client
    participant Scheduler as CoroutineScheduler
    participant TimeSource as ITimeSource
    participant Slot as CoroutineSlot
    participant Meta as CoroutineMetadata
    participant Stats as CoroutineStatistics

    Client->>Scheduler: Run(coroutine, tag, priority, group)
    alt coroutine is null
        Scheduler-->>Client: return default CoroutineHandle
    else coroutine valid
        Scheduler->>Scheduler: allocate handle and slotIndex
        Scheduler->>Slot: create CoroutineSlot
        Scheduler->>Meta: create CoroutineMetadata
        Scheduler->>Scheduler: AddTag(tag, handle)
        Scheduler->>Scheduler: AddGroup(group, handle)
        Scheduler->>Stats: RecordStart(priority, tag)
        Scheduler->>Scheduler: Prewarm(slotIndex)
        Scheduler->>Scheduler: ActiveCoroutineCount++
        Scheduler-->>Client: return handle
    end

    loop each frame
        Client->>Scheduler: Update()
        Scheduler->>TimeSource: Update()
        TimeSource-->>Scheduler: DeltaTime, CurrentTime
        Scheduler->>Scheduler: update Stats.ActiveCount, Stats.PausedCount
        Scheduler->>Scheduler: collect running slot indices
        Scheduler->>Scheduler: sort indices by Slot.Priority (desc)
        loop for each sorted slotIndex
            Scheduler->>Slot: advance enumerator
            alt coroutine finished
                Scheduler->>Scheduler: Complete(slotIndex)
                Scheduler->>Meta: read Priority, Tag, StartTime
                Scheduler->>TimeSource: CurrentTime
                Scheduler->>Stats: RecordComplete(executionTimeMs, Priority, Tag)
                Scheduler->>Scheduler: RemoveTag(handle)
                Scheduler->>Scheduler: RemoveGroup(handle)
                Scheduler->>Scheduler: wake waiting coroutines
                Scheduler->>Scheduler: ActiveCoroutineCount--
            else coroutine yields
                Scheduler->>Slot: update Waiting/State
            end
        end
    end
Loading

协程分组暂停、恢复和终止操作时序图

sequenceDiagram
    actor Client
    participant Scheduler as CoroutineScheduler

    Client->>Scheduler: PauseGroup(groupName)
    alt group exists
        Scheduler->>Scheduler: lookup _grouped[groupName]
        Scheduler->>Scheduler: for each handle in set
        loop handles
            Scheduler->>Scheduler: Pause(handle)
        end
        Scheduler-->>Client: return pausedCount
    else group missing
        Scheduler-->>Client: return 0
    end

    Client->>Scheduler: ResumeGroup(groupName)
    alt group exists
        Scheduler->>Scheduler: for each handle in set
        loop handles
            Scheduler->>Scheduler: Resume(handle)
        end
        Scheduler-->>Client: return resumedCount
    else group missing
        Scheduler-->>Client: return 0
    end

    Client->>Scheduler: KillGroup(groupName)
    alt group exists
        Scheduler->>Scheduler: for each handle in set
        loop handles
            Scheduler->>Scheduler: Kill(handle)
        end
        Scheduler-->>Client: return killedCount
    else group missing
        Scheduler-->>Client: return 0
    end
Loading

带分组、优先级和统计的协程调度器类图

classDiagram
    direction LR

    class CoroutineScheduler {
        <<sealed>>
        +CoroutineScheduler(ITimeSource timeSource, byte instanceId, int initialCapacity, bool enableStatistics)
        +int ActiveCoroutineCount
        +ICoroutineStatistics Statistics
        +event OnCoroutineException
        +CoroutineHandle Run(IEnumerator~IYieldInstruction~ coroutine, string tag, CoroutinePriority priority, string group)
        +void Update()
        +bool Pause(CoroutineHandle handle)
        +bool Resume(CoroutineHandle handle)
        +bool Kill(CoroutineHandle handle)
        +int PauseGroup(string group)
        +int ResumeGroup(string group)
        +int KillGroup(string group)
        +int GetGroupCount(string group)
        +int KillByTag(string tag)
        +int Clear()
        -Dictionary~CoroutineHandle,CoroutineMetadata~ _metadata
        -Dictionary~string,HashSet~CoroutineHandle~~ _tagged
        -Dictionary~string,HashSet~CoroutineHandle~~ _grouped
        -Dictionary~CoroutineHandle,HashSet~CoroutineHandle~~ _waiting
        -CoroutineSlot[] _slots
        -CoroutineStatistics _statistics
        -ITimeSource _timeSource
        -ILogger _logger
        -int _nextSlot
        -void Complete(int slotIndex)
        -void OnError(int slotIndex, Exception ex)
        -void AddTag(string tag, CoroutineHandle handle)
        -void RemoveTag(CoroutineHandle handle)
        -void AddGroup(string group, CoroutineHandle handle)
        -void RemoveGroup(CoroutineHandle handle)
    }

    class CoroutineMetadata {
        string Group
        CoroutinePriority Priority
        int SlotIndex
        double StartTime
        CoroutineState State
        string Tag
    }

    class CoroutineSlot {
        IEnumerator~IYieldInstruction~ Enumerator
        CoroutineHandle Handle
        bool HasStarted
        CoroutinePriority Priority
        CoroutineState State
        IYieldInstruction Waiting
    }

    class CoroutineStatistics {
        <<sealed>>
        +long TotalStarted
        +long TotalCompleted
        +long TotalFailed
        +int ActiveCount
        +int PausedCount
        +double AverageExecutionTimeMs
        +double MaxExecutionTimeMs
        +int GetCountByPriority(CoroutinePriority priority)
        +int GetCountByTag(string tag)
        +void Reset()
        +string GenerateReport()
        +void RecordStart(CoroutinePriority priority, string tag)
        +void RecordComplete(double executionTimeMs, CoroutinePriority priority, string tag)
        +void RecordFailure(CoroutinePriority priority, string tag)
        -Dictionary~CoroutinePriority,int~ _countByPriority
        -Dictionary~string,int~ _countByTag
        -object _lock
        -double _maxExecutionTimeMs
        -long _totalStarted
        -long _totalCompleted
        -long _totalFailed
        -long _totalExecutionTimeMs
    }

    class ICoroutineStatistics {
        <<interface>>
        +long TotalStarted
        +long TotalCompleted
        +long TotalFailed
        +int ActiveCount
        +int PausedCount
        +double AverageExecutionTimeMs
        +double MaxExecutionTimeMs
        +int GetCountByPriority(CoroutinePriority priority)
        +int GetCountByTag(string tag)
        +void Reset()
        +string GenerateReport()
    }

    class ITimeSource {
        <<interface>>
        +double CurrentTime
        +double DeltaTime
        +void Update()
    }

    class FakeTimeSource {
        <<sealed>>
        +double CurrentTime
        +double DeltaTime
        +void Update()
        +void Advance(double deltaTime)
        +void Reset()
    }

    class CoroutinePriority {
        <<enum>>
        Lowest
        Low
        Normal
        High
        Highest
    }

    class CoroutineHandle
    class CoroutineState
    class IYieldInstruction
    class ILogger

    CoroutineScheduler --> ITimeSource : uses
    CoroutineScheduler --> ICoroutineStatistics : exposes
    CoroutineScheduler --> CoroutineStatistics : owns (optional)
    CoroutineScheduler --> CoroutineMetadata : metadata
    CoroutineScheduler --> CoroutineSlot : slots
    CoroutineScheduler --> CoroutinePriority : scheduling

    CoroutineMetadata --> CoroutinePriority : priority
    CoroutineMetadata --> CoroutineState : state

    CoroutineSlot --> CoroutineHandle : handle
    CoroutineSlot --> CoroutinePriority : priority
    CoroutineSlot --> CoroutineState : state
    CoroutineSlot --> IYieldInstruction : enumerator yields

    CoroutineStatistics ..|> ICoroutineStatistics

    FakeTimeSource ..|> ITimeSource

    ICoroutineStatistics --> CoroutinePriority : in methods
    ICoroutineStatistics --> string : tag

    CoroutineScheduler --> CoroutineHandle : returns
    CoroutineScheduler --> IYieldInstruction : runs
    CoroutineScheduler --> ILogger : logging
    CoroutineStatistics --> string : report
Loading

File-Level Changes

Change Details Files
Extend CoroutineScheduler to support groups, priorities, and basic runtime statistics.
  • 新增可选的 enableStatistics 构造函数标志,并暴露返回 ICoroutineStatistics 实现的 Statistics 属性。
  • 在 CoroutineMetadata 和 CoroutineSlot 中跟踪协程优先级和分组信息,并记录以毫秒为单位的开始时间戳。
  • 扩展 Run(...) 方法签名以接受 priority 和 group,并将其接入元数据、标签/分组映射以及统计中的 RecordStart。
  • 引入按分组的批量操作(PauseGroup、ResumeGroup、KillGroup、GetGroupCount),由内部的 group->handle 映射支撑,并提供 AddGroup/RemoveGroup 帮助方法。
  • 在每次 Update 时刷新统计计数器,构建正在运行的槽位列表,按照优先级(降序)排序,并按该顺序执行。
  • 在协程完成或失败时,通过 RecordComplete/RecordFailure 更新统计,并确保分组和标签引用被清理。
  • 确保 Clear() 会重置分组状态,并调整 KillByTag 以直接在标签集合上操作,而不是复制句柄。
GFramework.Core/coroutine/CoroutineScheduler.cs
GFramework.Core/coroutine/CoroutineMetadata.cs
GFramework.Core/coroutine/CoroutineSlot.cs
Introduce a coroutine statistics implementation and public abstraction.
  • 定义 ICoroutineStatistics,用于暴露聚合计数、时间度量、按优先级/标签统计、重置以及报表生成等 API。
  • 实现 CoroutineStatistics,使用 Interlocked 的线程安全计数器,并使用加锁保护的字典来维护按优先级/标签计数以及最大执行时间。
  • 提供 RecordStart、RecordComplete 和 RecordFailure 方法,用以维护计数、平均与最大执行时间,并保持按优先级/标签的统计平衡。
  • 实现格式化的 GenerateReport(),包含总计数、当前活动/暂停数、时间统计,以及按优先级和标签排序的明细,同时提供 Reset() 方法用于清空所有状态。
GFramework.Core.Abstractions/coroutine/ICoroutineStatistics.cs
GFramework.Core/coroutine/CoroutineStatistics.cs
Add coroutine priority model and supporting tests and fake time source for deterministic scheduling tests.
  • 引入 CoroutinePriority 枚举,包含五个等级(Lowest、Low、Normal、High、Highest),供调度器和统计模块使用。
  • 新增实现 ITimeSource 的 FakeTimeSource,提供可控的 Advance/Reset 方法,用于在测试中实现确定性的时间推进。
  • 通过新的全局 using 将 NUnit 接入测试项目。
  • 添加覆盖统计行为的单元测试(启用/禁用、计数器、计时、按优先级/标签统计、重置、报表生成以及线程安全)。
  • 为分组管理添加单元测试(分组分配、PauseGroup/ResumeGroup/KillGroup 语义、分组计数,以及在完成时的清理)。
  • 为优先级调度添加单元测试,以验证不同优先级下的执行顺序,并确保相同优先级时按创建顺序执行。
GFramework.Core.Abstractions/coroutine/CoroutinePriority.cs
GFramework.Core.Tests/GlobalUsings.cs
GFramework.Core.Tests/coroutine/FakeTimeSource.cs
GFramework.Core.Tests/coroutine/CoroutineStatisticsTests.cs
GFramework.Core.Tests/coroutine/CoroutineGroupTests.cs
GFramework.Core.Tests/coroutine/CoroutinePriorityTests.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the
    pull request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Original review guide in English

Reviewer's Guide

Adds coroutine grouping and priority support to the scheduler, along with an opt-in statistics subsystem and supporting tests/utilities (including a fake time source).

Sequence diagram for running and completing a prioritized coroutine with statistics

sequenceDiagram
    actor Client
    participant Scheduler as CoroutineScheduler
    participant TimeSource as ITimeSource
    participant Slot as CoroutineSlot
    participant Meta as CoroutineMetadata
    participant Stats as CoroutineStatistics

    Client->>Scheduler: Run(coroutine, tag, priority, group)
    alt coroutine is null
        Scheduler-->>Client: return default CoroutineHandle
    else coroutine valid
        Scheduler->>Scheduler: allocate handle and slotIndex
        Scheduler->>Slot: create CoroutineSlot
        Scheduler->>Meta: create CoroutineMetadata
        Scheduler->>Scheduler: AddTag(tag, handle)
        Scheduler->>Scheduler: AddGroup(group, handle)
        Scheduler->>Stats: RecordStart(priority, tag)
        Scheduler->>Scheduler: Prewarm(slotIndex)
        Scheduler->>Scheduler: ActiveCoroutineCount++
        Scheduler-->>Client: return handle
    end

    loop each frame
        Client->>Scheduler: Update()
        Scheduler->>TimeSource: Update()
        TimeSource-->>Scheduler: DeltaTime, CurrentTime
        Scheduler->>Scheduler: update Stats.ActiveCount, Stats.PausedCount
        Scheduler->>Scheduler: collect running slot indices
        Scheduler->>Scheduler: sort indices by Slot.Priority (desc)
        loop for each sorted slotIndex
            Scheduler->>Slot: advance enumerator
            alt coroutine finished
                Scheduler->>Scheduler: Complete(slotIndex)
                Scheduler->>Meta: read Priority, Tag, StartTime
                Scheduler->>TimeSource: CurrentTime
                Scheduler->>Stats: RecordComplete(executionTimeMs, Priority, Tag)
                Scheduler->>Scheduler: RemoveTag(handle)
                Scheduler->>Scheduler: RemoveGroup(handle)
                Scheduler->>Scheduler: wake waiting coroutines
                Scheduler->>Scheduler: ActiveCoroutineCount--
            else coroutine yields
                Scheduler->>Slot: update Waiting/State
            end
        end
    end
Loading

Sequence diagram for coroutine group pause, resume, and kill operations

sequenceDiagram
    actor Client
    participant Scheduler as CoroutineScheduler

    Client->>Scheduler: PauseGroup(groupName)
    alt group exists
        Scheduler->>Scheduler: lookup _grouped[groupName]
        Scheduler->>Scheduler: for each handle in set
        loop handles
            Scheduler->>Scheduler: Pause(handle)
        end
        Scheduler-->>Client: return pausedCount
    else group missing
        Scheduler-->>Client: return 0
    end

    Client->>Scheduler: ResumeGroup(groupName)
    alt group exists
        Scheduler->>Scheduler: for each handle in set
        loop handles
            Scheduler->>Scheduler: Resume(handle)
        end
        Scheduler-->>Client: return resumedCount
    else group missing
        Scheduler-->>Client: return 0
    end

    Client->>Scheduler: KillGroup(groupName)
    alt group exists
        Scheduler->>Scheduler: for each handle in set
        loop handles
            Scheduler->>Scheduler: Kill(handle)
        end
        Scheduler-->>Client: return killedCount
    else group missing
        Scheduler-->>Client: return 0
    end
Loading

Class diagram for coroutine scheduler with grouping, priority, and statistics

classDiagram
    direction LR

    class CoroutineScheduler {
        <<sealed>>
        +CoroutineScheduler(ITimeSource timeSource, byte instanceId, int initialCapacity, bool enableStatistics)
        +int ActiveCoroutineCount
        +ICoroutineStatistics Statistics
        +event OnCoroutineException
        +CoroutineHandle Run(IEnumerator~IYieldInstruction~ coroutine, string tag, CoroutinePriority priority, string group)
        +void Update()
        +bool Pause(CoroutineHandle handle)
        +bool Resume(CoroutineHandle handle)
        +bool Kill(CoroutineHandle handle)
        +int PauseGroup(string group)
        +int ResumeGroup(string group)
        +int KillGroup(string group)
        +int GetGroupCount(string group)
        +int KillByTag(string tag)
        +int Clear()
        -Dictionary~CoroutineHandle,CoroutineMetadata~ _metadata
        -Dictionary~string,HashSet~CoroutineHandle~~ _tagged
        -Dictionary~string,HashSet~CoroutineHandle~~ _grouped
        -Dictionary~CoroutineHandle,HashSet~CoroutineHandle~~ _waiting
        -CoroutineSlot[] _slots
        -CoroutineStatistics _statistics
        -ITimeSource _timeSource
        -ILogger _logger
        -int _nextSlot
        -void Complete(int slotIndex)
        -void OnError(int slotIndex, Exception ex)
        -void AddTag(string tag, CoroutineHandle handle)
        -void RemoveTag(CoroutineHandle handle)
        -void AddGroup(string group, CoroutineHandle handle)
        -void RemoveGroup(CoroutineHandle handle)
    }

    class CoroutineMetadata {
        string Group
        CoroutinePriority Priority
        int SlotIndex
        double StartTime
        CoroutineState State
        string Tag
    }

    class CoroutineSlot {
        IEnumerator~IYieldInstruction~ Enumerator
        CoroutineHandle Handle
        bool HasStarted
        CoroutinePriority Priority
        CoroutineState State
        IYieldInstruction Waiting
    }

    class CoroutineStatistics {
        <<sealed>>
        +long TotalStarted
        +long TotalCompleted
        +long TotalFailed
        +int ActiveCount
        +int PausedCount
        +double AverageExecutionTimeMs
        +double MaxExecutionTimeMs
        +int GetCountByPriority(CoroutinePriority priority)
        +int GetCountByTag(string tag)
        +void Reset()
        +string GenerateReport()
        +void RecordStart(CoroutinePriority priority, string tag)
        +void RecordComplete(double executionTimeMs, CoroutinePriority priority, string tag)
        +void RecordFailure(CoroutinePriority priority, string tag)
        -Dictionary~CoroutinePriority,int~ _countByPriority
        -Dictionary~string,int~ _countByTag
        -object _lock
        -double _maxExecutionTimeMs
        -long _totalStarted
        -long _totalCompleted
        -long _totalFailed
        -long _totalExecutionTimeMs
    }

    class ICoroutineStatistics {
        <<interface>>
        +long TotalStarted
        +long TotalCompleted
        +long TotalFailed
        +int ActiveCount
        +int PausedCount
        +double AverageExecutionTimeMs
        +double MaxExecutionTimeMs
        +int GetCountByPriority(CoroutinePriority priority)
        +int GetCountByTag(string tag)
        +void Reset()
        +string GenerateReport()
    }

    class ITimeSource {
        <<interface>>
        +double CurrentTime
        +double DeltaTime
        +void Update()
    }

    class FakeTimeSource {
        <<sealed>>
        +double CurrentTime
        +double DeltaTime
        +void Update()
        +void Advance(double deltaTime)
        +void Reset()
    }

    class CoroutinePriority {
        <<enum>>
        Lowest
        Low
        Normal
        High
        Highest
    }

    class CoroutineHandle
    class CoroutineState
    class IYieldInstruction
    class ILogger

    CoroutineScheduler --> ITimeSource : uses
    CoroutineScheduler --> ICoroutineStatistics : exposes
    CoroutineScheduler --> CoroutineStatistics : owns (optional)
    CoroutineScheduler --> CoroutineMetadata : metadata
    CoroutineScheduler --> CoroutineSlot : slots
    CoroutineScheduler --> CoroutinePriority : scheduling

    CoroutineMetadata --> CoroutinePriority : priority
    CoroutineMetadata --> CoroutineState : state

    CoroutineSlot --> CoroutineHandle : handle
    CoroutineSlot --> CoroutinePriority : priority
    CoroutineSlot --> CoroutineState : state
    CoroutineSlot --> IYieldInstruction : enumerator yields

    CoroutineStatistics ..|> ICoroutineStatistics

    FakeTimeSource ..|> ITimeSource

    ICoroutineStatistics --> CoroutinePriority : in methods
    ICoroutineStatistics --> string : tag

    CoroutineScheduler --> CoroutineHandle : returns
    CoroutineScheduler --> IYieldInstruction : runs
    CoroutineScheduler --> ILogger : logging
    CoroutineStatistics --> string : report
Loading

File-Level Changes

Change Details Files
Extend CoroutineScheduler to support groups, priorities, and basic runtime statistics.
  • Add optional enableStatistics constructor flag and expose Statistics property returning an ICoroutineStatistics implementation.
  • Track coroutine priority and group in CoroutineMetadata and CoroutineSlot, including start timestamp in milliseconds.
  • Extend Run(...) signature to accept priority and group, wiring them into metadata, tag/group maps, and statistics RecordStart.
  • Introduce per-group bulk operations (PauseGroup, ResumeGroup, KillGroup, GetGroupCount) backed by an internal group->handle map with AddGroup/RemoveGroup helpers.
  • On each Update, refresh statistics counters, build a list of running slots, sort them by priority (descending), and iterate in that order for execution.
  • On coroutine completion or failure, update statistics via RecordComplete/RecordFailure and ensure group and tag references are cleaned up.
  • Ensure Clear() resets grouping state and adjust KillByTag to operate directly on the tag set instead of copying handles.
GFramework.Core/coroutine/CoroutineScheduler.cs
GFramework.Core/coroutine/CoroutineMetadata.cs
GFramework.Core/coroutine/CoroutineSlot.cs
Introduce a coroutine statistics implementation and public abstraction.
  • Define ICoroutineStatistics to expose aggregate counts, timing metrics, per-priority/tag counts, reset, and report generation APIs.
  • Implement CoroutineStatistics with thread-safe counters using Interlocked and lock-protected dictionaries for priority/tag counts and max execution time.
  • Provide RecordStart, RecordComplete, and RecordFailure methods to maintain counts, average and max execution time, and keep per-priority/tag tallies balanced.
  • Implement a formatted GenerateReport() including totals, current active/paused, timing stats, and sorted breakdowns by priority and tag, plus a Reset() method to clear all state.
GFramework.Core.Abstractions/coroutine/ICoroutineStatistics.cs
GFramework.Core/coroutine/CoroutineStatistics.cs
Add coroutine priority model and supporting tests and fake time source for deterministic scheduling tests.
  • Introduce CoroutinePriority enum with five levels (Lowest, Low, Normal, High, Highest) to be used by the scheduler and stats.
  • Add FakeTimeSource implementing ITimeSource with controllable Advance/Reset methods for deterministic time progression in tests.
  • Wire NUnit into test project via a new global using.
  • Add unit tests covering statistics behavior (enable/disable, counters, timing, priority/tag counts, reset, report generation, and thread-safety).
  • Add unit tests for group management (group assignment, PauseGroup/ResumeGroup/KillGroup semantics, group counts, and cleanup on completion).
  • Add unit tests for priority scheduling to verify execution order across different priorities and that equal priorities respect creation order.
GFramework.Core.Abstractions/coroutine/CoroutinePriority.cs
GFramework.Core.Tests/GlobalUsings.cs
GFramework.Core.Tests/coroutine/FakeTimeSource.cs
GFramework.Core.Tests/coroutine/CoroutineStatisticsTests.cs
GFramework.Core.Tests/coroutine/CoroutineGroupTests.cs
GFramework.Core.Tests/coroutine/CoroutinePriorityTests.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread GFramework.Core.Abstractions/coroutine/CoroutinePriority.cs Outdated
Comment thread GFramework.Core/coroutine/CoroutineScheduler.cs

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并提供了一些高层次反馈:

  • KillGroupKillByTag 中,你在遍历 HashSet<CoroutineHandle> 时调用 Kill,而 Kill 可能会通过 RemoveGroup/RemoveTag 间接从同一个集合中移除句柄,这有触发 InvalidOperationException 的风险;建议在遍历前先复制到数组(就像之前的 KillByTag 所做的那样)。
  • CoroutineStatistics 类的文档说明它是线程安全的,但 ActiveCountPausedCount 是普通的自动属性,由调度器直接更新,未使用 Interlocked 或锁,因此并发读取可能会看到撕裂或过期的值;要么让这些更新变成原子/线程安全的,要么在文档中澄清该接口只是部分线程安全。
  • CoroutineScheduler.Update 中,每帧通过 _metadata.Count(m => m.Value.State == CoroutineState.Paused) 计算 PausedCount 会带来每帧分配和 O(n) 的开销;你可以通过维护一个在 Pause/Resume 中更新的暂停计数器来避免这部分开销。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `KillGroup` and `KillByTag`, you iterate the `HashSet<CoroutineHandle>` while `Kill` can indirectly remove handles from the same set via `RemoveGroup/RemoveTag`, which risks `InvalidOperationException`; consider copying to an array (as the previous `KillByTag` did) before iterating.
- The `CoroutineStatistics` class is documented as thread‑safe, but `ActiveCount` and `PausedCount` are plain auto‑properties updated from the scheduler without interlocked or locking, so any concurrent readers may see torn or out‑of‑date values; either make these updates atomic/thread‑safe or clarify that the interface is only partially thread‑safe.
- In `CoroutineScheduler.Update`, computing `PausedCount` each frame with `_metadata.Count(m => m.Value.State == CoroutineState.Paused)` adds per‑frame allocations and O(n) cost; you can avoid this overhead by maintaining a paused counter updated in `Pause`/`Resume` instead.

## Individual Comments

### Comment 1
<location path="GFramework.Core/coroutine/CoroutineScheduler.cs" line_range="343" />
<code_context>
+        if (!_grouped.TryGetValue(group, out var handles))
+            return 0;
+
+        return handles.Count(Kill);
+    }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `handles.Count(Kill)` risks modifying the collection while iterating it.

In `KillByTag`, `handles` is the same `HashSet<CoroutineHandle>` stored in `_tagged`, and `Kill` is likely to call `RemoveTag`, mutating this set while `Count` is enumerating it. This can cause an `InvalidOperationException` ("Collection was modified"). Please keep the previous defensive pattern (e.g. copy to an array first) or otherwise ensure enumeration happens on a stable collection.
</issue_to_address>

### Comment 2
<location path="GFramework.Core/coroutine/CoroutineStatistics.cs" line_range="31-34" />
<code_context>
+    public long TotalFailed => Interlocked.Read(ref _totalFailed);
+
+    /// <inheritdoc />
+    public int ActiveCount { get; set; }
+
+    /// <inheritdoc />
+    public int PausedCount { get; set; }
+
+    /// <inheritdoc />
</code_context>
<issue_to_address>
**issue (bug_risk):** ActiveCount/PausedCount are not updated in a thread-safe way despite the class being documented as thread-safe.

These properties are written from `CoroutineScheduler.Update` with no synchronization and also modified in `Reset` under a lock, so they are neither consistently atomic nor consistently protected. That violates the documented thread-safety of `CoroutineStatistics` and can cause visibility races. Please either:
- back them with `Interlocked` fields (like the other counters), or
- make them part of the locked state, only accessing the backing fields under the existing `_lock`.

This ensures concurrent reads don’t race with writes.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这些评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点击 👍 或 👎,我会根据这些反馈改进后续的评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In KillGroup and KillByTag, you iterate the HashSet<CoroutineHandle> while Kill can indirectly remove handles from the same set via RemoveGroup/RemoveTag, which risks InvalidOperationException; consider copying to an array (as the previous KillByTag did) before iterating.
  • The CoroutineStatistics class is documented as thread‑safe, but ActiveCount and PausedCount are plain auto‑properties updated from the scheduler without interlocked or locking, so any concurrent readers may see torn or out‑of‑date values; either make these updates atomic/thread‑safe or clarify that the interface is only partially thread‑safe.
  • In CoroutineScheduler.Update, computing PausedCount each frame with _metadata.Count(m => m.Value.State == CoroutineState.Paused) adds per‑frame allocations and O(n) cost; you can avoid this overhead by maintaining a paused counter updated in Pause/Resume instead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `KillGroup` and `KillByTag`, you iterate the `HashSet<CoroutineHandle>` while `Kill` can indirectly remove handles from the same set via `RemoveGroup/RemoveTag`, which risks `InvalidOperationException`; consider copying to an array (as the previous `KillByTag` did) before iterating.
- The `CoroutineStatistics` class is documented as thread‑safe, but `ActiveCount` and `PausedCount` are plain auto‑properties updated from the scheduler without interlocked or locking, so any concurrent readers may see torn or out‑of‑date values; either make these updates atomic/thread‑safe or clarify that the interface is only partially thread‑safe.
- In `CoroutineScheduler.Update`, computing `PausedCount` each frame with `_metadata.Count(m => m.Value.State == CoroutineState.Paused)` adds per‑frame allocations and O(n) cost; you can avoid this overhead by maintaining a paused counter updated in `Pause`/`Resume` instead.

## Individual Comments

### Comment 1
<location path="GFramework.Core/coroutine/CoroutineScheduler.cs" line_range="343" />
<code_context>
+        if (!_grouped.TryGetValue(group, out var handles))
+            return 0;
+
+        return handles.Count(Kill);
+    }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `handles.Count(Kill)` risks modifying the collection while iterating it.

In `KillByTag`, `handles` is the same `HashSet<CoroutineHandle>` stored in `_tagged`, and `Kill` is likely to call `RemoveTag`, mutating this set while `Count` is enumerating it. This can cause an `InvalidOperationException` ("Collection was modified"). Please keep the previous defensive pattern (e.g. copy to an array first) or otherwise ensure enumeration happens on a stable collection.
</issue_to_address>

### Comment 2
<location path="GFramework.Core/coroutine/CoroutineStatistics.cs" line_range="31-34" />
<code_context>
+    public long TotalFailed => Interlocked.Read(ref _totalFailed);
+
+    /// <inheritdoc />
+    public int ActiveCount { get; set; }
+
+    /// <inheritdoc />
+    public int PausedCount { get; set; }
+
+    /// <inheritdoc />
</code_context>
<issue_to_address>
**issue (bug_risk):** ActiveCount/PausedCount are not updated in a thread-safe way despite the class being documented as thread-safe.

These properties are written from `CoroutineScheduler.Update` with no synchronization and also modified in `Reset` under a lock, so they are neither consistently atomic nor consistently protected. That violates the documented thread-safety of `CoroutineStatistics` and can cause visibility races. Please either:
- back them with `Interlocked` fields (like the other counters), or
- make them part of the locked state, only accessing the backing fields under the existing `_lock`.

This ensures concurrent reads don’t race with writes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/coroutine/CoroutineScheduler.cs Outdated
Comment thread GFramework.Core/coroutine/CoroutineStatistics.cs Outdated
GeWuYou added 3 commits March 6, 2026 12:36
- 将 CoroutinePriority 枚举的基础类型从 byte 移除
- 优化枚举类型的定义方式
- 引入 _pausedCount 字段直接跟踪暂停协程数量
- 将统计信息中的 ActiveCount 和 PausedCount 改为线程安全的原子操作
- 在暂停和恢复协程时直接更新 _pausedCount 计数
- 修复 KillGroup 方法中的并发修改异常问题
- 重置统计信息时使用原子操作清零计数字段
- 在KillByTag方法中创建句柄数组副本以避免集合被修改的异常
- 修复Complete方法的缩进格式问题
- 为WaitCommandSwitch添加默认分支以处理未知类型的等待指令
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant